package cn.kerui.auth.service;

import cn.dev33.satoken.jwt.StpLogicJwtForMixin;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.kerui.auth.event.vo.LoginInfoEvent;
import cn.kerui.common.core.constant.AuthConstants;
import cn.kerui.common.enums.LoginType;
import cn.kerui.common.exception.AuthException;
import cn.kerui.common.framework.util.crypto.EncipherUtil;
import cn.kerui.common.framework.util.lang.NumberUtil;
import cn.kerui.common.framework.util.lang.ServletUtil;
import cn.kerui.common.framework.util.lang.StringUtil;
import cn.kerui.common.framework.util.spring.util.SpringContextUtil;
import cn.kerui.common.helper.RequestHelper;
import cn.kerui.common.redis.constants.AuthRedisConstants;
import cn.kerui.common.redis.helper.RedisHelper;
import cn.kerui.common.satoken.utils.StpUtil;
import cn.kerui.repos.entities.manage.ManageUserLogin;
import cn.kerui.repos.mapper.manage.ManageUserLoginMapper;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * <p> 登录业务层 </p>
 * <p>创建于 2023/12/25 14:17 </p>
 *
 * @author yangkai
 * @version v1.0
 * @since 1.0.0
 */
@Service
public class LoginService {

    @Autowired
    private RedisHelper redisHelper;

    @Autowired
    private ManageUserLoginMapper manageUserLoginMapper;

    @Value("${sa-token.token-prefix:null}")
    private String prefix;

    /**
     * 登录校验
     */
    public void checkLogin(LoginType loginType, String device, String userName, Supplier<Boolean> supplier) {
        String errLoginKey = AuthRedisConstants.FAIL_LOGIN_NUM_KEY;
        // TODO 需要后期替换成系统参数
        Integer maxRetryCount = 3;
        Integer logTime = 5;

        // 校验现在是否登录重试次数达到最大限制
        Integer errorNum = NumberUtil.parseInt(redisHelper.getObj(errLoginKey, userName, String.class), 0);

        // 如果在锁定时间内登录，则不允许登录
        if (errorNum >= maxRetryCount) {
            recordLoginLog(userName, device, loginType.name(), AuthConstants.LOGIN_FAILED, "用户已被锁定");
            throw new AuthException("用户已被锁定, 请在{}分钟之后再试", logTime);
        }

        // 登录失败统计失败次数
        if (!supplier.get()) {
            errorNum++;
            redisHelper.setObj(errLoginKey, userName, errorNum, logTime, TimeUnit.MINUTES);

            if (errorNum >= maxRetryCount) {
                recordLoginLog(userName, device, loginType.name(), AuthConstants.LOGIN_FAILED, "用户已被锁定");
                throw new AuthException("用户已被锁定, 请在{}分钟之后再试", logTime);
            } else {
                recordLoginLog(userName, device, loginType.name(), AuthConstants.LOGIN_FAILED, "密码输入错误}");
                throw new AuthException("登录失败, 您还有{}次重试机会", maxRetryCount - errorNum);
            }
        }

        // 登录成功，删除登录错误次数
        redisHelper.delCacheByRedisKey(errLoginKey, userName);
    }

    /**
     * 发布登录日志事件
     * @param userName 用户名
     * @param deviceType 登录设备
     * @param grantType 登录方式
     * @param status 登录成功状态
     * @param message 提示信息
     */
    public void recordLoginLog(String userName, String deviceType, String grantType, Integer status, String message) {
        LoginInfoEvent loginInfoEvent = new LoginInfoEvent();
        loginInfoEvent.setUserName(userName);
        loginInfoEvent.setLoginType(AuthConstants.LOGIN_TYPE);
        loginInfoEvent.setStatus(status);
        loginInfoEvent.setDeviceType(deviceType);
        loginInfoEvent.setMessage(message);
        loginInfoEvent.setGrantType(grantType);
        loginInfoEvent.setHttpServletRequest(ServletUtil.getRequest());
        SpringContextUtil.getApplicationContext().publishEvent(loginInfoEvent);
    }

    /**
     * 退出登录
     */
    public void logout() {
        HttpServletRequest request = RequestHelper.getRequest();
        String tokenValue = ServletUtil.getHeader(request, AuthConstants.HTTP_HEADER_TOKEN, Charset.defaultCharset());
        String token = tokenValue.substring(prefix.length() + 1);
        JWT jwt = JWTUtil.parseToken(token);
        String device = String.valueOf(jwt.getPayload(AuthConstants.JWT_USER_LOGIN_DEVICE));
        String userId = String.valueOf(jwt.getPayload(AuthConstants.JWT_USER_LOGIN_ID));
        StpUtil.setStpLogic(new StpLogicJwtForMixin(device));

        try {
            ManageUserLogin manageUserLogin = manageUserLoginMapper.selectById(userId);

            recordLoginLog(manageUserLogin.getUserName(), device, AuthConstants.LOGIN_TYPE, AuthConstants.LOGIN_SUCCESS, "退出登录成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            StpUtil.logout();
        }
    }

    /**
     * 校验密码是否相等
     * @param password 密码
     * @param manageUserLogin 用户登录信息
     * @return
     */
    public Boolean checkPw(String password, ManageUserLogin manageUserLogin) {
        StringBuffer buffer = new StringBuffer();
        String saltAndContext = buffer.append(password)
                .append(StringUtil.AT)
                .append(manageUserLogin.getSalt())
                .toString();



        // 加密
        String encipherPassword = EncipherUtil.md5Encrypt(saltAndContext, manageUserLogin.getSalt());

        // 跟数据库查询出来的密码进行比对
        return encipherPassword.equals(manageUserLogin.getPassword());
    }
}
